package com.kinyx.framework.reflect;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import com.kinyx.framework.annotation.assist.CanNotNull;
import com.kinyx.framework.annotation.assist.CanNull;
import com.kinyx.framework.annotation.assist.ReturnNotNull;
import com.kinyx.framework.annotation.assist.ReturnNull;
import com.kinyx.framework.annotation.assist.Tester;
import com.kinyx.framework.annotation.assist.Throws;
import com.kinyx.framework.enums.impl.ErrorCodeX;
import com.kinyx.framework.exception.KRuntimeException;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;

/**
 * 类工具类
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class KClassUtils {

	/**
	 * 获取所有父类类型
	 * @param clazz 当前类型
	 * @return 父类类型列表
	 */
	@ReturnNotNull
	public static List<Class<?>> getExtendsClasses(@CanNotNull final Class<?> clazz) {
		if (clazz.equals(Object.class)) { return Collections.emptyList(); }
		final ArrayList<Class<?>> list = new ArrayList<>();
		for (Class<?> tmpClass = clazz;;) {
			tmpClass = tmpClass.getSuperclass();
			if ((tmpClass == null) || tmpClass.equals(Object.class)) { break; }
			list.add(tmpClass);
		}
		return list;
	}

	/**
	 * 判断是否继承自父类
	 * @param clazz 当前类型
	 * @param superClass 父类类型
	 * @return true：继承自父类；false：不继承自父类；
	 */
	@ReturnNotNull
	@Tester("com.kinyx.framework.reflect.KClassUtils_Tester_checkExtends")
	public static boolean checkExtends(@CanNotNull final Class<?> clazz, @CanNotNull final Class<?> superClass) {
		if (clazz.equals(superClass)) { return false; }
		if (superClass.equals(Object.class)) { return true; }
		if (clazz.equals(Object.class)) { return false; }
		final List<Class<?>> classes = getExtendsClasses(clazz);
		for (final Class<?> c : classes) { if (c.equals(superClass)) { return true; } }
		return false;
	}

	/**
	 * 获取所有接口类型
	 * @param clazz 当前类型
	 * @return 接口类型列表
	 */
	@ReturnNotNull
	public static List<Class<?>> getImplementsClasses(@CanNotNull final Class<?> clazz) {
		if (clazz.equals(Object.class)) { return Collections.emptyList(); }
		final Set<Class<?>> set = new TreeSet<>((c1, c2) -> c1.equals(c2) ? 0 : 1);
		for (Class<?> tmpClass = clazz; (tmpClass != null) && !tmpClass.equals(Object.class); tmpClass = tmpClass.getSuperclass()) {
			final Class<?>[] iArr = tmpClass.getInterfaces();// 取类上所有接口
			for (final Class<?> i : iArr) {
				set.add(i);
				final Class<?>[] iiArr = i.getInterfaces();// 取该接口上所有接口
				set.addAll(Arrays.asList(iiArr));
				for (final Class<?> ii : iiArr) {
					set.addAll(getImplementsClasses(ii));
				}
			}
		}
		return new ArrayList<>(set);
	}

	/**
	 * 判断是否实现接口类
	 * @param clazz 当前类型
	 * @param interfaceClass 接口类类型
	 * @return true：实现接口类；false：未实现接口类；
	 */
	@ReturnNotNull
	@Tester("com.kinyx.framework.reflect.KClassUtils_Tester_checkImplements")
	public static boolean checkImplements(@CanNotNull final Class<?> clazz, @CanNotNull final Class<?> interfaceClass) {
		if (!interfaceClass.isInterface()) { throw new IllegalArgumentException(); }
		if (clazz.equals(Object.class)) { return false; }
		final List<Class<?>> interfaces = getImplementsClasses(clazz);
		for (final Class<?> c : interfaces) {
			if (c.equals(interfaceClass)) { return true; }
		}
		return false;
	}

	/**
	 * 获取serialVersionUID值
	 * @param clazz 类型
	 * @return serialVersionUID值
	 */
	@ReturnNull("未实现Serializable接口")
	@ReturnNull("未定义约定的serialVersionUID字段")
	//	@Throws(at = "KClassUtils.getSerialVersionUID(File)", value = "IllegalArgumentException", when = "Field.get(Object)")
	//	@Throws(at = "KClassUtils.getSerialVersionUID(File)", value = "IllegalAccessException", when = "Field.get(Object)")
	@Tester("com.kinyx.framework.reflect.KClassUtils_Tester_getSerialVersionUID")
	public static Long getSerialVersionUID(@CanNotNull final Class<?> clazz) {
		if (!checkImplements(clazz, Serializable.class)) { return null; }
		Field field;
		try {
			field = clazz.getDeclaredField("serialVersionUID");
		} catch (final Exception e) {
			return null;
		}
		final int modifiers = field.getModifiers();
		if (!Modifier.isStatic(modifiers) || !Modifier.isFinal(modifiers)) { return null; }
		if (!field.getType().equals(long.class)) { return null; }
		if (!field.isAccessible()) { field.setAccessible(true); }
		Object serialVersionUID = null;
		try {
			serialVersionUID = field.get(null);
		} catch (final IllegalArgumentException e) {// never happens
			throw new KRuntimeException(e, ErrorCodeX.NeverHappens);
		} catch (final IllegalAccessException e) {// never happens
			throw new KRuntimeException(e, ErrorCodeX.NeverHappens);
		}
		return serialVersionUID == null ? null : (long) serialVersionUID;
	}

	/**
	 * 强制转换
	 * @param <T> 泛型
	 * @param clazz 类型
	 * @param obj 对象
	 * @return 对象
	 */
	@ReturnNull("obj == null")
	@SuppressWarnings("unchecked")
	public static <T> T with(@CanNotNull final Class<T> clazz, @CanNull final Object obj) {
		return obj == null ? null : (T) obj;
	}

	/**
	 * Returns the {@code Class} object associated with the class or
	 * interface with the given string name.  Invoking this method is
	 * equivalent to:
	 *
	 * <blockquote>
	 *  {@code Class.forName(className, true, currentLoader)}
	 * </blockquote>
	 *
	 * where {@code currentLoader} denotes the defining class loader of
	 * the current class.
	 *
	 * <p> For example, the following code fragment returns the
	 * runtime {@code Class} descriptor for the class named
	 * {@code java.lang.Thread}:
	 *
	 * <blockquote>
	 *   {@code Class t = Class.forName("java.lang.Thread")}
	 * </blockquote>
	 * <p>
	 * A call to {@code forName("X")} causes the class named
	 * {@code X} to be initialized.
	 *
	 * @param      className   the fully qualified name of the desired class.
	 * @return     the {@code Class} object for the class with the
	 *             specified name.
	 */
	@Throws(at = "KClassUtils.forName(String)", value = "ClassNotFoundException", when = "Class.forName(String)")
	public static Class<?> forName(final String className) {
		try {
			return Class.forName(className);
		} catch (final ClassNotFoundException e) {
			throw new KRuntimeException(e);
		}
	}

}
